#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/errno.h>
#define CNAME "a_back"
#define RED_BASE 0xC001A000
#define BLUE_BASE 0xC001B000
#define GREEN_BASE 0xC001E000
#define BEEP_BASE 0xC001C000

struct class *cls;
struct device *dev;
struct cdev *cdev;

unsigned int *red_base = NULL;
unsigned int *blue_base = NULL;
unsigned int *green_base = NULL;
unsigned int *beep_base = NULL;

int major = 0, minor = 0;
int ret, condition = 0;
const int count = 1;
char kbuf[4];

int drv_open(struct inode *inode, struct file *file)
{
    printk("drv_open\n");
    return 0;
}

ssize_t drv_write(struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
    printk("drv_write\n");
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret)
    {
        printk("copy to user faild\n");
        return -EINVAL;
    }
#if 1
    if (kbuf[0] == 1)
    {
        *red_base |= (1 << 28);
        *blue_base &= ~(1 << 12);
        *green_base &= ~(1 << 13);
    }
    else
    {
        *red_base &= ~(1 << 28);
    }
    if (kbuf[1] == 1)
    {
        *blue_base |= (1 << 12);
        *red_base &= ~(1 << 28);
        *green_base &= ~(1 << 13);
    }
    else
    {
        *blue_base &= ~(1 << 12);
    }
    if (kbuf[2] == 1)
    {
        *green_base |= (1 << 13);
        *red_base &= ~(1 << 28);
        *blue_base &= ~(1 << 12);
    }
    else
    {
        *green_base &= ~(1 << 13);
    }
    if (kbuf[3] == 1)
    {
        *beep_base |= (1 << 14);
        *green_base &= ~(1 << 13);
        *red_base &= ~(1 << 28);
        *blue_base &= ~(1 << 12);
    }
    else
    {
        *beep_base &= ~(1 << 14);
    }

#endif

    return size;
}

ssize_t drv_read(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
    printk("drv_read\n");
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret)
    {
        printk("copy to user faild\n");
        return -EINVAL;
    }
    return size;
}

int drv_close(struct inode *inode, struct file *file)
{
    printk("drv_close\n");
    return 0;
}

const struct file_operations fops = {
    .open = drv_open,
    .write = drv_write,
    .read = drv_read,
    .release = drv_close,
};

static int __init drv_init(void)
{
    dev_t devm;
    cdev = cdev_alloc();
    if (cdev == NULL)
    {
        printk("alloc memory faild\n");
        ret = -ENOMEM;
        goto ERR_STEP0;
    }
    cdev_init(cdev, &fops);
    ret = alloc_chrdev_region(&devm, minor, count, CNAME);
    if (ret)
    {
        printk("alloc register number failed\n");
        goto ERR_STEP1;
    }

    major = MAJOR(devm);
    minor = MINOR(devm);

    ret = cdev_add(cdev, MKDEV(major, minor), count);
    if (ret)
    {
        printk("char device regist failed\n");
        goto ERR_STEP2;
    }

    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls))
    {
        printk("class create failed\n");
        ret = PTR_ERR(cls);
        goto ERR_STEP3;
    }

    dev = device_create(cls, NULL, MKDEV(major, minor), NULL, "a53_led_drive");
    if (IS_ERR(dev))
    {
        printk("device create failed\n");
        ret = PTR_ERR(dev);
        goto ERR_STEP4;
    }

    red_base = ioremap(RED_BASE, 36);
    if (red_base == NULL)
    {
        printk("red ioremap error\n");
        return -ENOMEM;
    }
    blue_base = ioremap(BLUE_BASE, 36);
    if (blue_base == NULL)
    {
        printk("blue ioremap error\n");
        return -ENOMEM;
    }
    green_base = ioremap(GREEN_BASE, 36);
    if (green_base == NULL)
    {
        printk("green ioremap error\n");
        return -ENOMEM;
    }
    beep_base = ioremap(BEEP_BASE, 36);
    if (beep_base == NULL)
    {
        printk("beep ioremap error\n");
        return -ENOMEM;
    }
    writel((readl(red_base) & (~(1 << 28))), red_base);
    writel((readl(red_base + 1) | (1 << 28)), (red_base + 1));
    writel((readl(red_base + 9) & (~(3 << 28))), (red_base + 9));

    writel((readl(blue_base) & (~(1 << 12))), blue_base);
    writel((readl(blue_base + 1) | (1 << 12)), (blue_base + 1));
    writel((readl(blue_base + 8) & (~(3 << 24))), (blue_base + 8));
    writel((readl(blue_base + 8) | (1 << 25)), (blue_base + 8));

    writel((readl(green_base) & (~(1 << 13))), green_base);
    writel((readl(green_base + 1) | (1 << 13)), (green_base + 1));
    writel((readl(green_base + 8) & (~(3 << 26))), (green_base + 8));

    writel((readl(beep_base) & (~(1 << 14))), beep_base);
    writel((readl(beep_base + 1) | (1 << 14)), (beep_base + 1));
    writel((readl(beep_base + 8) & (~(3 << 28))), (beep_base + 8));
    writel((readl(beep_base + 8) | (1 << 28)), (beep_base + 8));
    return 0;

ERR_STEP4:
    class_destroy(cls);
ERR_STEP3:
    cdev_del(cdev);
ERR_STEP2:
    unregister_chrdev_region(MKDEV(major, minor), count);
ERR_STEP1:
    kfree(cdev);
ERR_STEP0:
    return ret;
}

static void __exit drv_exit(void)
{
    iounmap(red_base);
    iounmap(green_base);
    iounmap(blue_base);
    class_destroy(cls);
    cdev_del(cdev);
    unregister_chrdev(major, CNAME);
}

module_init(drv_init);
module_exit(drv_exit);
MODULE_LICENSE("GPL");
